//--------------------------------------------------------------------------------------
// File: ShindeMeshDemo.cpp
//--------------------------------------------------------------------------------------
#include "DXUT.h"
#include "DXUTcamera.h"
#include "SkinnedMesh.h"

const D3DXCOLOR WHITE(1.0f, 1.0f, 1.0f, 1.0f);

struct Mtrl
{
	Mtrl()
		:ambient(WHITE), diffuse(WHITE), spec(WHITE), specPower(8.0f){}
	Mtrl(const D3DXCOLOR& a, const D3DXCOLOR& d, 
		const D3DXCOLOR& s, float power)
		:ambient(a), diffuse(d), spec(s), specPower(power){}

	D3DXCOLOR ambient;
	D3DXCOLOR diffuse;
	D3DXCOLOR spec;
	float specPower;
};

struct DirLight
{
	D3DXCOLOR ambient;
	D3DXCOLOR diffuse;
	D3DXCOLOR spec;
	D3DXVECTOR3 dirW;
};

SkinnedMesh *g_skinnedMesh;
IDirect3DTexture9 *g_meshTexture;
CFirstPersonCamera g_camera;
Mtrl g_mtrl;
DirLight g_dirLight;
D3DXMATRIX g_world;


ID3DXEffect* g_FX;
D3DXHANDLE   g_hTech;
D3DXHANDLE   g_hWVP;
D3DXHANDLE   g_hWorldInvTrans;
D3DXHANDLE   g_hFinalXForms;
D3DXHANDLE   g_hEyePos;
D3DXHANDLE   g_hWorld;
D3DXHANDLE   g_hTex;
D3DXHANDLE   g_hMtrl;
D3DXHANDLE   g_hLight;


void BuildFX(IDirect3DDevice9 *pd3dDevice);
void DrawMesh(IDirect3DDevice9 *pd3dDevice);

//--------------------------------------------------------------------------------------
// Rejects any D3D9 devices that aren't acceptable to the app by returning false
//--------------------------------------------------------------------------------------
bool CALLBACK IsD3D9DeviceAcceptable( D3DCAPS9* pCaps, D3DFORMAT AdapterFormat, D3DFORMAT BackBufferFormat,
	bool bWindowed, void* pUserContext )
{
	// Typically want to skip back buffer formats that don't support alpha blending
	IDirect3D9* pD3D = DXUTGetD3D9Object();
	if( FAILED( pD3D->CheckDeviceFormat( pCaps->AdapterOrdinal, pCaps->DeviceType,
		AdapterFormat, D3DUSAGE_QUERY_POSTPIXELSHADER_BLENDING,
		D3DRTYPE_TEXTURE, BackBufferFormat ) ) )
		return false;

	return true;
}


//--------------------------------------------------------------------------------------
// Before a device is created, modify the device settings as needed
//--------------------------------------------------------------------------------------
bool CALLBACK ModifyDeviceSettings( DXUTDeviceSettings* pDeviceSettings, void* pUserContext )
{
	return true;
}


//--------------------------------------------------------------------------------------
// Create any D3D9 resources that will live through a device reset (D3DPOOL_MANAGED)
// and aren't tied to the back buffer size
//--------------------------------------------------------------------------------------
HRESULT CALLBACK OnD3D9CreateDevice( IDirect3DDevice9* pd3dDevice, const D3DSURFACE_DESC* pBackBufferSurfaceDesc,
	void* pUserContext )
{
	HRESULT hr;
	g_dirLight.dirW    = D3DXVECTOR3(1.0f, 1.0f, 2.0f);
	D3DXVec3Normalize(&g_dirLight.dirW, &g_dirLight.dirW);
	g_dirLight.ambient = D3DXCOLOR(0.5f, 0.5f, 0.5f, 1.0f);
	g_dirLight.diffuse = D3DXCOLOR(0.8f, 0.8f, 0.8f, 1.0f);
	g_dirLight.spec    = D3DXCOLOR(0.8f, 0.8f, 0.8f, 1.0f);

	g_mtrl.ambient = WHITE*0.9f;
	g_mtrl.diffuse = WHITE*0.6f;
	g_mtrl.spec    = WHITE*0.6f;
	g_mtrl.specPower = 48.0f;

	// Load the skinned mesh and its texture.
	g_skinnedMesh = new SkinnedMesh(L"tiny.x");
	g_skinnedMesh->onCreateDevice(pd3dDevice);

	V(D3DXCreateTextureFromFile(pd3dDevice, L"Tiny_skin.bmp", &g_meshTexture));

	// Scale the mesh down.
	D3DXMatrixScaling(&g_world, 0.01f, 0.01f, 0.01f);

	BuildFX(pd3dDevice);

	g_camera.SetViewParams(&D3DXVECTOR3(0.0f, 0.0f, -8.0f), &D3DXVECTOR3(0.0f, 0.0f, 0.0f));
	return S_OK;
}


//--------------------------------------------------------------------------------------
// Create any D3D9 resources that won't live through a device reset (D3DPOOL_DEFAULT) 
// or that are tied to the back buffer size 
//--------------------------------------------------------------------------------------
HRESULT CALLBACK OnD3D9ResetDevice( IDirect3DDevice9* pd3dDevice, const D3DSURFACE_DESC* pBackBufferSurfaceDesc,
	void* pUserContext )
{
	g_camera.SetProjParams(D3DX_PI / 4, pBackBufferSurfaceDesc->Width / (float)pBackBufferSurfaceDesc->Height,
		1.0f, 1000.0f);
	return S_OK;
}


//--------------------------------------------------------------------------------------
// Handle updates to the scene.  This is called regardless of which D3D API is used
//--------------------------------------------------------------------------------------
void CALLBACK OnFrameMove( double fTime, float fElapsedTime, void* pUserContext )
{
	g_camera.FrameMove(fElapsedTime);
	g_skinnedMesh->update(fElapsedTime);
}


//--------------------------------------------------------------------------------------
// Render the scene using the D3D9 device
//--------------------------------------------------------------------------------------
void CALLBACK OnD3D9FrameRender( IDirect3DDevice9* pd3dDevice, double fTime, float fElapsedTime, void* pUserContext )
{
	HRESULT hr;

	// Clear the render target and the zbuffer 
	V( pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_ARGB( 0, 45, 50, 170 ), 1.0f, 0 ) );

	// Render the scene
	if( SUCCEEDED( pd3dDevice->BeginScene() ) )
	{
		DrawMesh(pd3dDevice);
		V( pd3dDevice->EndScene() );
	}
}


//--------------------------------------------------------------------------------------
// Handle messages to the application 
//--------------------------------------------------------------------------------------
LRESULT CALLBACK MsgProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam,
	bool* pbNoFurtherProcessing, void* pUserContext )
{
	g_camera.HandleMessages(hWnd, uMsg, wParam, lParam);
	return 0;
}


//--------------------------------------------------------------------------------------
// Release D3D9 resources created in the OnD3D9ResetDevice callback 
//--------------------------------------------------------------------------------------
void CALLBACK OnD3D9LostDevice( void* pUserContext )
{
}


//--------------------------------------------------------------------------------------
// Release D3D9 resources created in the OnD3D9CreateDevice callback 
//--------------------------------------------------------------------------------------
void CALLBACK OnD3D9DestroyDevice( void* pUserContext )
{
	g_skinnedMesh->onDestoryDevice();
	delete g_skinnedMesh;
	g_meshTexture->Release();
	g_FX->Release();
}


//--------------------------------------------------------------------------------------
// Initialize everything and go into a render loop
//--------------------------------------------------------------------------------------
INT WINAPI wWinMain( HINSTANCE, HINSTANCE, LPWSTR, int )
{
	// Enable run-time memory check for debug builds.
#if defined(DEBUG) | defined(_DEBUG)
	_CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
#endif

	// Set the callback functions
	DXUTSetCallbackD3D9DeviceAcceptable( IsD3D9DeviceAcceptable );
	DXUTSetCallbackD3D9DeviceCreated( OnD3D9CreateDevice );
	DXUTSetCallbackD3D9DeviceReset( OnD3D9ResetDevice );
	DXUTSetCallbackD3D9FrameRender( OnD3D9FrameRender );
	DXUTSetCallbackD3D9DeviceLost( OnD3D9LostDevice );
	DXUTSetCallbackD3D9DeviceDestroyed( OnD3D9DestroyDevice );
	DXUTSetCallbackDeviceChanging( ModifyDeviceSettings );
	DXUTSetCallbackMsgProc( MsgProc );
	DXUTSetCallbackFrameMove( OnFrameMove );

	// TODO: Perform any application-level initialization here

	// Initialize DXUT and create the desired Win32 window and Direct3D device for the application
	DXUTInit( true, true ); // Parse the command line and show msgboxes
	DXUTSetHotkeyHandling( true, true, true );  // handle the default hotkeys
	DXUTSetCursorSettings( true, true ); // Show the cursor and clip it when in full screen
	DXUTCreateWindow( L"EmptyProject" );
	DXUTCreateDevice( true, 640, 480 );

	// Start the render loop
	DXUTMainLoop();

	// TODO: Perform any application-level cleanup here

	return DXUTGetExitCode();
}


void BuildFX(IDirect3DDevice9 *pd3dDevice)
{
	HRESULT hr;
	// Create the FX from a .fx file.
	ID3DXBuffer* errors = 0;
	V(D3DXCreateEffectFromFile(pd3dDevice, L"vblend2.fx", 
		0, 0, D3DXSHADER_DEBUG, 0, &g_FX, &errors));
	if( errors )
		MessageBoxA(0, (char*)errors->GetBufferPointer(), 0, 0);

	// Obtain handles.
	g_hTech            = g_FX->GetTechniqueByName("VBlend2Tech");
	g_hWVP             = g_FX->GetParameterByName(0, "gWVP");
	g_hWorldInvTrans   = g_FX->GetParameterByName(0, "gWorldInvTrans");
	g_hFinalXForms     = g_FX->GetParameterByName(0, "gFinalXForms");
	g_hMtrl            = g_FX->GetParameterByName(0, "gMtrl");
	g_hLight           = g_FX->GetParameterByName(0, "gLight");
	g_hEyePos          = g_FX->GetParameterByName(0, "gEyePosW");
	g_hWorld           = g_FX->GetParameterByName(0, "gWorld");
	g_hTex             = g_FX->GetParameterByName(0, "gTex");
}

void DrawMesh(IDirect3DDevice9 *pd3dDevice)
{
	HRESULT hr;
	// Set FX Parameters.  In particular, for this demo note that we set the 
	// final transformation matrix array for vertex blending.

	V(g_FX->SetMatrixArray(g_hFinalXForms, g_skinnedMesh->getFinalXFormArray(), g_skinnedMesh->numBones()));
	V(g_FX->SetValue(g_hLight, &g_dirLight, sizeof(DirLight)));
	D3DXMATRIX view = *g_camera.GetViewMatrix();
	D3DXMATRIX proj = *g_camera.GetProjMatrix();
	V(g_FX->SetMatrix(g_hWVP, &(g_world*view*proj)));
	D3DXMATRIX worldInvTrans;
	D3DXMatrixInverse(&worldInvTrans, 0, &g_world);
	D3DXMatrixTranspose(&worldInvTrans, &worldInvTrans);
	V(g_FX->SetMatrix(g_hWorldInvTrans, &worldInvTrans));
	V(g_FX->SetMatrix(g_hWorld, &g_world));
	V(g_FX->SetValue(g_hMtrl, &g_mtrl, sizeof(Mtrl)));
	V(g_FX->SetTexture(g_hTex, g_meshTexture));

	V(g_FX->SetTechnique(g_hTech));
	UINT numPasses = 0;
	V(g_FX->Begin(&numPasses, 0));
	V(g_FX->BeginPass(0));

	g_skinnedMesh->draw();

	V(g_FX->EndPass());
	V(g_FX->End());
}